﻿#if NETFX_CORE && !WinRT
#define WinRT
#endif


    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;


namespace DailyFx.UI 
{
	/// <summary>
	///   A markup interface for register to handle messages.
	/// </summary>
	public interface IMessageHandler
	{
	}

	/// <summary>
	///		registering to handle a particular type of message.
	/// </summary>
	public interface IMessageHandler<TMessage> : IMessageHandler
	{  //don't use contravariance here !!

		/// <summary>
		///   Handles the message.
		/// </summary>
		void Handle(TMessage message);
	}


	/// <summary>
	///		register which can handle a particular type of message and uses coroutine .
	/// </summary>
	public interface ICoroutinedMessageHandler<TMessage> : IMessageHandler
	{  //don't use contravariance here !!

		/// <summary>
		///  Handles the message via CoroutineSystem.
		/// </summary>
		IEnumerable<ICommand> Handle ( TMessage message );
	}

    /// <summary>
	///   Enables loosely-coupled broadcasting of and registering to messages/events.
    /// </summary>
    public interface IMediator 
	{
        /// <summary>
        ///   Gets or sets the default broadcasting thread marshaller.
        /// </summary>
        Action<System.Action> BroadcastingThreadMarshaller { get; set; }

        /// <summary>
        ///		Searches the subscribed handlers to check if we have one for the message type supplied.
        /// </summary>
        bool IfHandlerExists ( Type messageType );

        /// <summary>
        ///   Registers an instance to all messages/events declared through implementations of <see cref = "IMessageHandler{T}" />
        /// </summary>
        void Register ( object handler );

        /// <summary>
		///   Unregisters the instance from all messages/events.
        /// </summary>
        void Unregister (object handler );

        /// <summary>
        ///   Broadcasts a specified message .
        /// </summary>
        /// <remarks>
        ///   Uses the default thread marshaller during broadcasting .
        /// </remarks>
        void Broadcast ( object message );

        /// <summary>
		///   Broadcasts a specified message .
        /// </summary>
        void Broadcast ( object message, Action<System.Action> marshal );
    }


    /// <summary>
    ///   Enables loosely-coupled broadcasting of and registering to messages/events.
    /// </summary>
    public class Mediator : IMediator
	{
		/// <summary>
		///		Contains all registered handlers .
		/// </summary>
        private readonly List<MsgHandler> _handlers = new List<MsgHandler>();


        /// <summary>
        ///   The default thread marshaller used for broadcasting ;
        /// </summary>
        public static Action<System.Action> DefaultBroadcastingThreadMarshaller = action => action();

        /// <summary>
		///		Processing of handler results on broadcasting thread.
        /// </summary>
        public static Action<object, object> HandlerTaskProcessing = (target, result) => { };



        /// <summary>
        ///   Initializes a new instance of the <see cref = "Mediator" /> class.
        /// </summary>
        public Mediator () 
		{
            BroadcastingThreadMarshaller = DefaultBroadcastingThreadMarshaller;
        }

        /// <summary>
        ///		Gets or sets the default broadcasting thread marshaller.
        /// </summary>
        public Action<System.Action> BroadcastingThreadMarshaller 
		{ 
			get; 
			set; 
		}

		/// <summary>
		///		Searches the subscribed handlers to check if we have one for the message type supplied.
		/// </summary>
        public bool IfHandlerExists ( Type messageType )
		{
                return this._handlers.Any(handler => handler.Handles(messageType) & !handler.IsDead);
        }

        /// <summary>
        ///   Registers an instance to all messages/events declared through implementations of <see cref = "IMessageHandler{T}" />
        /// </summary>
        public virtual void Register( object handler ) 
		{
            if ( handler == null) 
			{
                throw new ArgumentNullException( "handler" );
            }
            lock( this._handlers ) 
			{
                if ( this._handlers.Any(x => x.Matches(handler)) )
				{
                    return;
                }

                this._handlers.Add ( new MsgHandler(handler) );
            }
        }

        /// <summary>
        ///   Unregisters the instance from all messages/events.
        /// </summary>
        public virtual void Unregister ( object handler )
		{
			if (handler == null)
			{
                throw new ArgumentNullException( "handler" );
            }
            lock( this._handlers ) 
			{
				var found = _handlers.FirstOrDefault( x => x.Matches(handler) );
                if (found != null)
				{
                   this._handlers.Remove(found);
                }
            }
        }

        /// <summary>
        ///		Broadcasts a specified message .
        /// </summary>
        /// <remarks>
        ///		Does not marshall the the broadcasting to any special thread by default.
        /// </remarks>
        public virtual void Broadcast ( object message ) 
		{
            if ( message == null ) {
                throw new ArgumentNullException( "message" );
            }
            Broadcast ( message, BroadcastingThreadMarshaller );
        }

        /// <summary>
		///   Broadcasts a specified message .
        /// </summary>
        /// <param name = "marshal">
		///		Allows the mediator to provide a custom thread marshaller for the message broadcasting .
		///	</param>
        public virtual void Broadcast ( object message, Action<System.Action> marshal )
		{
            if (message == null){
                throw new ArgumentNullException("message");
            }
            if (marshal == null) {
                throw new ArgumentNullException("marshal");
            }

            MsgHandler[] msgHandler;
            lock ( this._handlers ) 
			{
				msgHandler = this._handlers.ToArray();
            }

            marshal( () => 
			{
                var messageType = message.GetType();

				var dead = msgHandler
							.Where(handler => !handler.TryHandle(messageType, message))
							.ToList();

                if( dead.Any() )
				{
                    lock( this._handlers ) 
					{
                        dead.Apply( x => this._handlers.Remove(x) );
                    }
                }
            });
        }

    }



	internal class MsgHandler /// wrapper for referencing the <see cref="IMessageHandler"/>(s)
	{
		/// <summary>
		///		the weak reference to the <see cref="IMessageHandler"/>
		/// </summary>
		readonly WeakReference _reference;

		/// <summary>
		///		
		/// </summary>
		readonly Dictionary<Type, MethodInfo> _supportedMethods = new Dictionary<Type, MethodInfo>();

	
		/// <summary>
		///		c'tor
		/// </summary>
		/// <param name="handler"></param>
		public MsgHandler ( object handler )
		{
			this._reference = new WeakReference( handler );

#if WinRT
                var typeInfo = typeof(IMessageHandler).GetTypeInfo();
                var interfaces = handler.GetType().GetTypeInfo().ImplementedInterfaces
										.Where( x => typeInfo.IsAssignableFrom(x.GetTypeInfo()) && x.GetTypeInfo().IsGenericType) ;

                foreach ( var @interface in interfaces )
				{
                    var type = @interface.GenericTypeArguments [0];
                    var method = @interface.GetTypeInfo().DeclaredMethods.First(x => x.Name == "Handle");
					
					// add this handle method of the type .
                    supportedHandlers[type] = method;
                }
#else
			var interfaces = handler.GetType().GetInterfaces()
									.Where ( x => typeof(IMessageHandler).IsAssignableFrom(x) && x.IsGenericType );

			foreach ( var @interface in interfaces )
			{
				var type = @interface.GetGenericArguments() [0];
				var method = @interface.GetMethod( "Handle" );
				
				// add this handle method of the type .
				this._supportedMethods[type] = method;
			}
#endif
		}

		/// <summary>
		///		Checks if the reference to the handler is missing .
		/// </summary>
		public bool IsDead
		{
			get { return _reference.Target == null; }
		}

		/// <summary>
		///		Is target reference matches ?
		/// </summary>
		public bool Matches ( object instance )
		{
			return this._reference.Target == instance;
		}

		/// <summary>
		///		Can the type of message be handled ?
		/// </summary>
		public bool Handles ( Type messageType )
		{
			return this._supportedMethods.Any( pair => pair.Key.IsAssignableFrom(messageType) );
		}

		/// <summary>
		///		Try to handle this message .
		/// </summary>
		/// <returns></returns>
		public bool TryHandle ( Type messageType, object message )
		{
			var target = this._reference.Target;
			if ( target == null )
			{
				return false;
			}

			foreach ( var pair in this._supportedMethods )
			{
				if ( pair.Key.IsAssignableFrom(messageType) )
				{
					var result = pair.Value.Invoke(target, new[] { message });
					if (result != null)
					{
						Mediator.HandlerTaskProcessing (target, result);
					}
				}
			}

			return true;
		}

		
	}
}
